{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "42d27e6f",
   "metadata": {},
   "source": [
    "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3edf125e",
   "metadata": {},
   "source": [
    "# Building a Social Network for Games on Amazon Neptune\n",
    "This notebook shows how to use Amazon Neptune and Gremlin to build a graph of players and game titles. This can be used for a games platform to make friends and games recommendations based on a player's network and games preferences.\n",
    "## Background\n",
    "Over the past decades, there has been a massive increase in the popularity of online multiplayer video games. Players want to play their favorite games online with their friends, find new games to explore, and connect with other players to socialize. Game developers and publishers that help players discover new communities and game titles that match their interests can create a better experience for players and increase player loyalty and retention on their platforms.\n",
    "\n",
    "Amazon Neptune is a fast, fully managed database service powering graph use cases such as social networks and recommendation engines. Graph databases allow you to traverse relationships between entities, like users and games, and make suggestions based on the behavior of similar users. Neptune is optimized for storing billions of relationships and makes it easy to build and run applications that work with highly connected datasets."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a652da3d",
   "metadata": {},
   "source": [
    "## Getting Started\n",
    "In this section, we'll load the graph and set some visualization options. Then, we'll use Gremlin queries to inspect the data model used throughout the solution."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da5c40b9",
   "metadata": {},
   "source": [
    "### Load data\n",
    "The cell below loads the example social network into your Neptune cluster. When you run the cell, it will automatically install the `games-social-graph` dataset into your graph. This can take a few minutes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cb527316",
   "metadata": {},
   "outputs": [],
   "source": [
    "%seed --model Property_Graph --language gremlin --dataset games-social-graph --run"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f7f1c4f",
   "metadata": {},
   "source": [
    "### Set visualization options\n",
    "The cell below sets the property values to display on the graph visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d135be09",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_node_labels = '{\"Game\":\"title\",\"User\":\"username\"}'\n",
    "my_edge_labels = '{\"rating\":\"stars\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ef9004a",
   "metadata": {},
   "source": [
    "The cell below configures the visualization to use specific colors and icons for the different parts of the data model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f175a57d",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture\n",
    "%%graph_notebook_vis_options\n",
    "\n",
    "{\n",
    "  \"groups\": {\n",
    "    \"Game\": {\n",
    "      \"shape\": \"icon\",\n",
    "      \"icon\": {\n",
    "        \"face\": \"FontAwesome\",\n",
    "        \"code\": \"\\uf11b\",\n",
    "        \"color\": \"#ff9900\",\n",
    "        \"scaling\": {\n",
    "          \"label\": \"true\"\n",
    "        }\n",
    "      }\n",
    "    },\n",
    "    \"User\": {\n",
    "      \"shape\": \"icon\",\n",
    "      \"icon\": {\n",
    "        \"face\": \"FontAwesome\",\n",
    "        \"code\": \"\\uf2bd\",\n",
    "        \"color\": \"#0749af\",\n",
    "        \"scaling\": {\n",
    "          \"label\": \"true\"\n",
    "        }\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1e6a10e",
   "metadata": {},
   "source": [
    "### Data model\n",
    "The dataset in our example is made up of 50 fictitious players and 50 fictitious game titles. A player is represented by a `User` vertex. Each `User` vertex has a property `username`. A game title is represented by the `Game` vertex. Each `Game` vertex has the properties `title`, `genre`, `ratingSum`, and `ratingCount`.\n",
    "\n",
    "A `User` can have a `friendOf` edge to another `User`, representing that two users are mutually friends. A `User` can have a `rating` edge to a `Game` with a property `stars`, which represents a 1-to-5 star rating scale that the user gave to the game, where 5 is the most favorable rating.\n",
    "\n",
    "**DISCLAIMER:** All game titles and usernames in this sample dataset were randomly generated and are fictional. Any resemblance to real-life entities, past or present, is purely coincidental."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b41deeb3",
   "metadata": {},
   "source": [
    "### Visualize the data model\n",
    "The cell below illustrates a graph with:\n",
    "* 2 `User` vertices\n",
    "* 1 `friendOf` edge connecting the 2 `User` vertices\n",
    "* 1 `Game` vertex \n",
    "* 1 `rating` edge from a `User` vertex with property `star`\n",
    "\n",
    "After you run the cell below, choose the **Graph** tab to see the graph visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "983ca1e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V('1050')\n",
    " .both('friendOf').limit(1)\n",
    " .outE('rating').limit(1)\n",
    " .inV()\n",
    " .path().by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21b28f19",
   "metadata": {},
   "source": [
    "## Exploring the social network\n",
    "Using the sample dataset, we can explore the relationships between players, friends, and game titles to make recommendations. For example, we can suggest new games for a player to try or new friends for a player to add.\n",
    "### Suggest new friends with mutual friends\n",
    "We can suggest new friends to a user by identifying people they are likely to know through their network. By traversing the graph, we look for second degree friends (friends of friends). We can rank the likelihood that a user might know a second degree friend by the number of mutual friends they share.\n",
    "\n",
    "The cell below illustrates the graph with a `User`, their friends, and their second degree friends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44883ec9",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V('1034').as('user')\n",
    " .both('friendOf').aggregate('friends') // get friends of user\n",
    " .both('friendOf').where(P.neq('user')).where(P.without('friends')) // get friends of friends who are not user or friends of user\n",
    " .path().by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0934e86b",
   "metadata": {},
   "source": [
    "The cell below lists the top 3 second degree friends that a user is likely to know ranked by the number of mutual friends they share."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c65a77d",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V('1034').as('user')\n",
    " .both('friendOf').aggregate('friends')\n",
    " .both('friendOf').where(P.neq('user')).where(P.without('friends'))\n",
    " .groupCount().by('username')\n",
    " .order(local).by(values, desc)\n",
    " .unfold()\n",
    " .limit(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "900872ef",
   "metadata": {},
   "source": [
    "### Suggest new friends with similar preferences in games\n",
    "We can suggest new friends to a user by identifying people that enjoyed playing the same games. By traversing the graph, we look for games that the user gave a 5-star rating, and then find other users who also gave the same games 5-star ratings. We can rank how likely they are to become friends by the number of games they mutually gave 5-star ratings.\n",
    "\n",
    "The cell below illustrates the graph with a `User`, the games they gave 5-star ratings to, and other users who also gave those games 5-star ratings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb667903",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V('1011').as('user')\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV() // games that user gave 5-star rating\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV().where(neq('user')).not(both('friendOf').hasId('1011')) // other users that gave 5-star and is not user or friends of user\n",
    " .path().by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bec7948",
   "metadata": {},
   "source": [
    "The cell below lists the top 3 users that share similar preference in games ranked by the number of games they both gave 5-star ratings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa953283",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V('1011').as('user')\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV()\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV().where(neq('user')).not(both('friendOf').hasId('1011'))\n",
    " .groupCount().by('username')\n",
    " .order(local).by(values, desc)\n",
    " .unfold()\n",
    " .limit(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "965f88b1",
   "metadata": {},
   "source": [
    "### Suggest new games rated highly by friends\n",
    "We can suggest new games for a user to try by identifying games that the user's friends gave 5-star ratings. By traversing the graph, we look for a user's friends, and then find games that those friends gave 5-star ratings. We can rank the likelihood that the user might enjoy a game by the number of friends who gave 5-star ratings.\n",
    "\n",
    "The cell below illustrates the graph with a `User`, their friends, and other games that received 5-star ratings from these friends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0fb9926a",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V('1008').as('user')\n",
    " .both('friendOf').as('friends') // friends of user\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV().not(inE('rating').outV().hasId('1008')) // games that received 5-star ratings from friends but not from user\n",
    " .path()\n",
    " .by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "318e59e2",
   "metadata": {},
   "source": [
    "The cell below lists the top 3 games that the user is likely to enjoy ranked by the number of friends that gave 5-star ratings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1734f8b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V('1008').as('user')\n",
    " .both('friendOf').as('friends')\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV().not(inE('rating').outV().hasId('1008'))\n",
    " .groupCount().by('title')\n",
    " .order(local).by(values, desc)\n",
    " .unfold()\n",
    " .limit(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5769175a",
   "metadata": {},
   "source": [
    "### Suggest new games rated highly by similar players\n",
    "We can suggest new games for a user to try by identifying games that players with similar interests gave 5-star ratings. By traversing the graph, we look for a games that a user gave 5-star ratings, then find users that gave 5-star ratings to those games, and then look for other games that received 5-star ratings from those players. We can rank the likelihood that the user might enjoy a game by the number of similar players who gave 5-star ratings.\n",
    "\n",
    "The cell below illustrates the graph with a `User`, their favorite games, other players who gave 5-star ratings to those games, and other games that received 5-star ratings from these players."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15da4b85",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V('1026').as('user')\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV() // games that user gave 5-star rating\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV().where(neq('user')) // other users who gave the same games 5-star ratings\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV().not(inE('rating').outV().hasId('1026')) // games that got 5-star ratings from other users but has not been rated by user\n",
    " .path()\n",
    " .by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad1e6507",
   "metadata": {},
   "source": [
    "The cell below lists the top 10 games that the user is likely to enjoy ranked by the number of similar players that gave 5-star ratings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31a466c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V('1026').as('user')\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV()\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV().where(neq('user'))\n",
    " .outE('rating').has('stars', 5)\n",
    " .inV().not(inE('rating').outV().hasId('1026'))\n",
    " .groupCount().by('title')\n",
    " .order(local).by(values, desc)\n",
    " .unfold()\n",
    " .limit(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abf2eb8d",
   "metadata": {},
   "source": [
    "### Show friends who liked a game\n",
    "When a user is considering a new game to play, we can show them a list of friends who gave the game a 5-star rating.\n",
    "\n",
    "The cell below illustrates a graph with a `Game` that does not have a `rating` edge from a `User` and the friends of that user who have given a 5-star rating to the game."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d26ef851",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d $my_node_labels -de $my_edge_labels -l 40\n",
    "\n",
    "g.V().hasLabel('Game').has('title','Blasterdroid')\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV()\n",
    " .both('friendOf').hasId('1002')\n",
    " .path()\n",
    " .by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "334a49d3",
   "metadata": {},
   "source": [
    "The cell below lists the friends that gave a 5-star rating to a new game that a user is considering to play."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3bbd411d",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V().hasLabel('Game').has('title','Blasterdroid')\n",
    " .inE('rating').has('stars', 5)\n",
    " .outV().filter(both('friendOf').hasId('1002'))\n",
    " .values('username')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86487299",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "This notebook demonstrated how to use Amazon Neptune to build a social network for a games platform. We used a sample dataset with users and games and explored the friends relationship between users and the ratings connecting users and games. We traversed the graph to suggest new friends and games to users based on their existing friends and the games they rated."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bbdf924f",
   "metadata": {},
   "source": [
    "### (Optional) Cleaning up\n",
    "The cell below drops the example dataset from your graph.\n",
    "\n",
    "**NOTE:** Because the dataset used in this example is relatively small, the below cell can delete all the data in this graph. For larger datasets, the drop query could take longer than desired. To quickly reset a Neptune graph, you can use the `%db_reset` line magic or [use the Neptune fast reset API](https://docs.aws.amazon.com/neptune/latest/userguide/manage-console-fast-reset.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6641bf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "\n",
    "g.V().drop()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df28b291",
   "metadata": {},
   "source": [
    "## Additional Resources\n",
    "The examples in this notebook showed how to develop a social network data model for a games platform. To build a social network solution using Amazon Neptune, we recommend the following resources:\n",
    "* [Getting Started with Amazon Neptune](https://docs.aws.amazon.com/neptune/latest/userguide/graph-get-started.html)\n",
    "* [Amazon Web Services Reference Architectures for Using Graph Databases](https://github.com/aws-samples/aws-dbs-refarch-graph/)\n",
    "* [Amazon Neptune Developer Resources](https://aws.amazon.com/neptune/developer-resources/)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
